<?php

/** @class: InputFilter (PHP4 & PHP5, with comments)
  * @project: PHP Input Filter
  * @date: 10-05-2005
  * @version: 1.2.2_php4/php5
  * @author: Daniel Morris
  * @contributors: Gianpaolo Racca, Ghislain Picard, Marco Wandschneider, Chris Tobin and Andrew Eddie.
  * @copyright: Daniel Morris
  * @email: dan@rootcube.com
  * @license: GNU General Public License (GPL)
  */
class InputFilter {
  var $tagsArray = array('a', 'strong','blockquote','em','i','img','u','ul','li');
  var $attrArray = array('href', 'target', 'rel', 'name', 'title', 'selected','src', 'alt');

  var $tagsMethod = 0;		// default = 0
  var $attrMethod = 0;		// default = 0

  var $xssAuto = 1;           // default = 1
  var $tagBlacklist = array('applet', 'body', 'bgsound', 'base', 'basefont', 'embed', 'frame', 'frameset', 
                            'head', 'html', 'id', 'iframe', 'ilayer', 'layer', 'link', 'meta', 'name', 
                            'object', 'script', 'style', 'title', 'xml', 'src', 'width', 'height', 'alt', 
                            'b', 'img', 'i', 'ul', 'li', 'font', 'param');
  var $attrBlacklist = array('action', 'background', 'codebase', 'dynsrc', 'lowsrc', 'width','height',);  // also will strip ALL event handlers

  /**
	  * Constructor for inputFilter class. Only first parameter is required.
	  * @access constructor
	  * @param Array $tagsArray - list of user-defined tags
	  * @param Array $attrArray - list of user-defined attributes
	  * @param int $tagsMethod - 0= allow just user-defined, 1= allow all but user-defined
	  * @param int $attrMethod - 0= allow just user-defined, 1= allow all but user-defined
	  * @param int $xssAuto - 0= only auto clean essentials, 1= allow clean blacklisted tags/attr
	  */
  function inputFilter() {
  }

  public static function cp1251_utf8($word){
    $cyr_lower_chars = array(
    'е','щ','ф','ц','у','ж','э',
    'н','г','ш','ү','з','к','ъ',
    'й','ы','б','ө','а','х','р',
    'о','л','д','п','я','ч','ё',
    'с','м','и','т','ь','в','ю',);

    $latin_lower_chars = array(
    'å','ù','ô','ö','ó','æ','ý',
    'í','ã','ø','¿','ç','ê','ú',
    'é','û','á','º','à','õ','ð',
    'î','ë','ä','ï','ÿ','÷','¸',
    'ñ','ì','è','ò','ü','â','þ',);

    $cyr_upper_chars = array(
    'Е','Щ','Ф','Ц','У','Ж','Э',
    'Н','Г','Ш','Ү','З','К','Ъ',
    'Й','Ы','Б','Ө','А','Х','Р',
    'О','Л','Д','П','Я','Ч','Ё',
    'С','М','И','Т','Ь','В','Ю',);

    $latin_upper_chars = array(
    'Å','Ù','Ô','Ö','Ó','Æ','Ý',
    'Í','Ã','Ø','¯','Ç','Ê','Ú',
    'É','Û','Á','ª','À','Õ','Ð',
    'Î','Ë','Ä','Ï','ß','×','¨',
    'Ñ','Ì','È','Ò','Ü','Â','Þ',);

    //replacing lower cyrillic
    $word = str_replace($latin_lower_chars, $cyr_lower_chars, $word);
    //replacing upper cyrillic
    $word = str_replace($latin_upper_chars, $cyr_upper_chars, $word);
    return $word;
  }

  /**
	  * Method to be called by another php script. Processes for XSS and specified bad code.
	  * @access public
	  * @param Mixed $source - input string/array-of-string to be 'cleaned'
	  * @return String $source - 'cleaned' version of input parameter
	  */
  function process($source) {
    $source = str_replace('script','',$source);
    
    
    // clean all elements in this array
    if (is_array($source)) {
      foreach($source as $key => $value)
      // filter element for XSS and other 'bad' code etc.
      if (is_string($value)) $source[$key] = $this->remove($this->decode($value));
      return $source;
      // clean this string
    } else if (is_string($source)) {
      // filter source for XSS and other 'bad' code etc.
      return $this->remove($this->decode($source));
      // return parameter as given
    } else return $source;
  }

  /**
	  * Internal method to iteratively remove all unwanted tags and attributes
	  * @access protected
	  * @param String $source - input string to be 'cleaned'
	  * @return String $source - 'cleaned' version of input parameter
	  */
  function remove($source) {
    $loopCounter=0;
    // provides nested-tag protection
    while($source != $this->filterTags($source)) {
      $source = $this->filterTags($source);
      $loopCounter++;
    }
    return $source;
  }

  /**
	  * Internal method to strip a string of certain tags
	  * @access protected
	  * @param String $source - input string to be 'cleaned'
	  * @return String $source - 'cleaned' version of input parameter
	  */
  function filterTags($source) {
    // filter pass setup
    $preTag = NULL;
    $postTag = $source;
    // find initial tag's position
    $tagOpen_start = strpos($source, '<');
    // interate through string until no tags left
    while($tagOpen_start !== FALSE) {
      // process tag interatively
      $preTag .= substr($postTag, 0, $tagOpen_start);
      $postTag = substr($postTag, $tagOpen_start);
      $fromTagOpen = substr($postTag, 1);
      // end of tag
      $tagOpen_end = strpos($fromTagOpen, '>');
      if ($tagOpen_end === false) break;
      // next start of tag (for nested tag assessment)
      $tagOpen_nested = strpos($fromTagOpen, '<');
      if (($tagOpen_nested !== false) && ($tagOpen_nested < $tagOpen_end)) {
        $preTag .= substr($postTag, 0, ($tagOpen_nested+1));
        $postTag = substr($postTag, ($tagOpen_nested+1));
        $tagOpen_start = strpos($postTag, '<');
        continue;
      }
      $tagOpen_nested = (strpos($fromTagOpen, '<') + $tagOpen_start + 1);
      $currentTag = substr($fromTagOpen, 0, $tagOpen_end);
      $tagLength = strlen($currentTag);
      if (!$tagOpen_end) {
        $preTag .= $postTag;
        $tagOpen_start = strpos($postTag, '<');
      }
      // iterate through tag finding attribute pairs - setup
      $tagLeft = $currentTag;
      $attrSet = array();
      $currentSpace = strpos($tagLeft, ' ');
      // is end tag
      if (substr($currentTag, 0, 1) == "/") {
        $isCloseTag = TRUE;
        list($tagName) = explode(' ', $currentTag);
        $tagName = substr($tagName, 1);
        // is start tag
      } else {
        $isCloseTag = FALSE;
        list($tagName) = explode(' ', $currentTag);
      }
      // excludes all "non-regular" tagnames OR no tagname OR remove if xssauto is on and tag is blacklisted
      if ((!preg_match("/^[a-z][a-z0-9]*$/i",$tagName)) || (!$tagName) || ((in_array(strtolower($tagName), $this->tagBlacklist)) && ($this->xssAuto))) {
        $postTag = substr($postTag, ($tagLength + 2));
        $tagOpen_start = strpos($postTag, '<');
        // don't append this tag
        continue;
      }
      // this while is needed to support attribute values with spaces in!
      while ($currentSpace !== FALSE) {
        $fromSpace = substr($tagLeft, ($currentSpace+1));
        $nextSpace = strpos($fromSpace, ' ');
        $openQuotes = strpos($fromSpace, '"');
        $closeQuotes = strpos(substr($fromSpace, ($openQuotes+1)), '"') + $openQuotes + 1;
        // another equals exists
        if (strpos($fromSpace, '=') !== FALSE) {
          // opening and closing quotes exists
          if (($openQuotes !== FALSE) && (strpos(substr($fromSpace, ($openQuotes+1)), '"') !== FALSE))
          $attr = substr($fromSpace, 0, ($closeQuotes+1));
          // one or neither exist
          else $attr = substr($fromSpace, 0, $nextSpace);
          // no more equals exist
        } else $attr = substr($fromSpace, 0, $nextSpace);
        // last attr pair
        if (!$attr) $attr = $fromSpace;
        // add to attribute pairs array
        $attrSet[] = $attr;
        // next inc
        $tagLeft = substr($fromSpace, strlen($attr));
        $currentSpace = strpos($tagLeft, ' ');
      }
      // appears in array specified by user
      $tagFound = in_array(strtolower($tagName), $this->tagsArray);
      // remove this tag on condition
      if ((!$tagFound && $this->tagsMethod) || ($tagFound && !$this->tagsMethod)) {
        // reconstruct tag with allowed attributes
        if (!$isCloseTag) {
          $attrSet = $this->filterAttr($attrSet);
          $preTag .= '<' . $tagName;
          for ($i = 0; $i < count($attrSet); $i++)
          $preTag .= ' ' . $attrSet[$i];
          // reformat single tags to XHTML
          if (strpos($fromTagOpen, "</" . $tagName)) $preTag .= '>';
          else $preTag .= ' />';
          // just the tagname
        } else $preTag .= '</' . $tagName . '>';
      }
      // find next tag's start
      $postTag = substr($postTag, ($tagLength + 2));
      $tagOpen_start = strpos($postTag, '<');
    }
    // append any code after end of tags
    $preTag .= $postTag;
    return $preTag;
  }

  /**
	  * Internal method to strip a tag of certain attributes
	  * @access protected
	  * @param Array $attrSet
	  * @return Array $newSet
	  */
  function filterAttr($attrSet) {
    $newSet = array();
    // process attributes
    for ($i = 0; $i <count($attrSet); $i++) {
      // skip blank spaces in tag
      if (!$attrSet[$i]) continue;
      // split into attr name and value
      $attrSubSet = explode('=', trim($attrSet[$i]));
      list($attrSubSet[0]) = explode(' ', $attrSubSet[0]);
      // removes all "non-regular" attr names AND also attr blacklisted
      if ((!eregi("^[a-z]*$",$attrSubSet[0])) || (($this->xssAuto) && ((in_array(strtolower($attrSubSet[0]), $this->attrBlacklist)) || (substr($attrSubSet[0], 0, 2) == 'on'))))
      continue;
      // xss attr value filtering
      if ($attrSubSet[1]) {
        // strips unicode, hex, etc
        $attrSubSet[1] = str_replace('&#', '', $attrSubSet[1]);
        // strip normal newline within attr value
        $attrSubSet[1] = preg_replace('/\s+/', '', $attrSubSet[1]);
        // strip double quotes
        $attrSubSet[1] = str_replace('"', '', $attrSubSet[1]);
        // [requested feature] convert single quotes from either side to doubles (Single quotes shouldn't be used to pad attr value)
        if ((substr($attrSubSet[1], 0, 1) == "'") && (substr($attrSubSet[1], (strlen($attrSubSet[1]) - 1), 1) == "'"))
        $attrSubSet[1] = substr($attrSubSet[1], 1, (strlen($attrSubSet[1]) - 2));
        // strip slashes
        $attrSubSet[1] = stripslashes($attrSubSet[1]);
      }
      // auto strip attr's with "javascript:
      if (	((strpos(strtolower($attrSubSet[1]), 'expression') !== false) &&	(strtolower($attrSubSet[0]) == 'style')) ||
      (strpos(strtolower($attrSubSet[1]), 'javascript:') !== false) ||
      (strpos(strtolower($attrSubSet[1]), 'behaviour:') !== false) ||
      (strpos(strtolower($attrSubSet[1]), 'vbscript:') !== false) ||
      (strpos(strtolower($attrSubSet[1]), 'mocha:') !== false) ||
      (strpos(strtolower($attrSubSet[1]), 'livescript:') !== false)
      ) continue;

      // if matches user defined array
      $attrFound = in_array(strtolower($attrSubSet[0]), $this->attrArray);
      // keep this attr on condition
      if ((!$attrFound && $this->attrMethod) || ($attrFound && !$this->attrMethod)) {
        // attr has value
        if ($attrSubSet[1]) $newSet[] = $attrSubSet[0] . '="' . $attrSubSet[1] . '"';
        // attr has decimal zero as value
        else if ($attrSubSet[1] == "0") $newSet[] = $attrSubSet[0] . '="0"';
        // reformat single attributes to XHTML
        else $newSet[] = $attrSubSet[0] . '="' . $attrSubSet[0] . '"';
      }
    }
    return $newSet;
  }

  /**
	  * Try to convert to plaintext
	  * @access protected
	  * @param String $source
	  * @return String $source
	  */
  function decode($source) {
    // url decode
    $source = html_entity_decode($source, ENT_QUOTES, "ISO-8859-1");
    // convert decimal
    $source = preg_replace('/&#(\d+);/me',"chr(\\1)", $source);				// decimal notation
    // convert hex
    $source = preg_replace('/&#x([a-f0-9]+);/mei',"chr(0x\\1)", $source);	// hex notation
    return $source;
  }

  /**
	  * Method to be called by another php script. Processes for SQL injection
	  * @access public
	  * @param Mixed $source - input string/array-of-string to be 'cleaned'
	  * @param Buffer $connection - An open MySQL connection
	  * @return String $source - 'cleaned' version of input parameter
	  */
  function safeSQL($source, &$connection) {
    // clean all elements in this array
    if (is_array($source)) {
      foreach($source as $key => $value)
      // filter element for SQL injection
      if (is_string($value)) $source[$key] = $this->quoteSmart($this->decode($value), $connection);
      return $source;
      // clean this string
    } else if (is_string($source)) {
      // filter source for SQL injection
      if (is_string($source)) return $this->quoteSmart($this->decode($source), $connection);
      // return parameter as given
    } else return $source;
  }

  /**
	  * @author Chris Tobin
	  * @author Daniel Morris
	  * @access protected
	  * @param String $source
	  * @param Resource $connection - An open MySQL connection
	  * @return String $source
	  */
  function quoteSmart($source, &$connection) {
    // strip slashes
    if (get_magic_quotes_gpc()) $source = stripslashes($source);
    // quote both numeric and text
    $source = $this->escapeString($source, $connection);
    return $source;
  }

  /**
	  * @author Chris Tobin
	  * @author Daniel Morris
	  * @access protected
	  * @param String $source
	  * @param Resource $connection - An open MySQL connection
	  * @return String $source
	  */	
  function escapeString($string, &$connection) {
    // depreciated function
    if (version_compare(phpversion(),"4.3.0", "<")) mysql_escape_string($string);
    // current function
    else mysql_real_escape_string($string);
    return $string;
  }
}

?>